결측치

결측지(Missing value)는 누락된 값, 비어 있는 값을 의미함.
결측치가 있으면 함수가 적용되지 않거나 분석 결과가 왜곡되는 문제 발생

R에서 결측치의 정의

  • NA (Not Available) : R에서 결측치를 처리하는 Logical value
  • NaN (Not a Number) : NA와 비슷하게 생겼으나, 이는 계산이 불가능한 경우를 나타내는 numeric value (예: 분모를 0으로 나누는 경우)
  • NA와 ’NA’는 다름 : ’NA’는 ’NA’라는 character value임


결측치 관련 함수

  • is.na(): vector object의 원소에 대해 결측치이면 TRUE, 결측치가 아니면 FALSE를 반환
  • summary(): vector object의 원소 중 결측치가 있는 경우 7번째 원소로 결측치의 개수 카운팅
y <- c(1:3, NA)

# 결측치 확인
is.na(y)
is.na("NA")

# 결측치 개수 카운팅
sum(is.na(y))   
summary(y)


결측치 제거

  • na.rm=TRUE 옵션을 지원하지 않는 함수 존재 \(\rightarrow\) 함수 help문에서 결측치 제거 옵션 확인 필요. na.rm을 지원하지 않을 경우, filter()로 결측치 제거 후 함수 적용
    • filter(): 특정 행 추출
    • %>%: pipe operator, chain operator라고 함. %>% 앞에 있는 출력 결과를 뒤에 있는 함수에게 보내주는 역할 (ctrl+shift+M 입력)
  • na.omit(): 데이터에서 하나라도 결측치가 있는 경우, 해당 row를 제거한 뒤, 결측치가 없는 row만 반환
  • complete.cases(): 각 row에서 결측값이 없는 경우 TRUE, 하나라도 결측치가 있는 경우 FALSE 반환
weight <- c(65.4, 55, 380, 72.2, 51, NA)
height <- c(170, 155, NA, 173, 161, 166)
gender <- c(1, 2, 1, 1, 2, 2)
gender <- factor(gender, labels=c("M", "F"))
dxdate <- c("2040/09/01", "2040/09/01", "2040/09/05", "2040/09/14", "2040/10/11", "2040/11/11")
patient <- data.frame(weight, height, gender, dxdate)
patient
str(patient)

# 방법 1
library(dplyr)                      # ctrl + shift + M
patient %>%  filter(!is.na(weight) & !is.na(height))

# 방법 2
na.omit(patient)                    # 결측치 있는 행 모두 제거

# 방법 3 
complete.cases(patient)             # 결측치 없는 행 TRUE 반환
patient[complete.cases(patient), ]  # na.omit(patient)와 동일한 결과 


결측치 대체

데이터가 크고 결측치가 얼마 없는 경우에는 결측치를 제거하고 분석해도 크게 문제 없음.
그러나 데이터가 작고 결측치가 많은 경우, 결측치를 제거하면 많은 정보가 손실되어 분석 결과가 왜곡되는 문제 발생.
따라서 결측치 제거 대신 다른 값을 채워 넣는 방법을 ’결측치 대체법(Imputation)’이라 함.

  • 대표값(평균, 최빈값 등)으로 일괄 대체
  • 통계 분석 기법으로 결측치의 예측값을 추정하여 대체


# 결측치 확인
table(is.na(patient$weight))
## 
## FALSE  TRUE 
##     5     1
# 결측치 제외한 몸무게 평균 산출
mean(patient$weight, na.rm = TRUE)
## [1] 124.72
# 몸무게가 NA면 평균값인 125로 대체
patient$weight1 <- ifelse(is.na(patient$weight), 125, patient$weight)

# 결측치 확인
table(is.na(patient$weight1))
## 
## FALSE 
##     6


결측치 연산

  • 결측치가 포함된 데이터의 연산 중 사칙연산 및 논리연산의 경우 결측치가 포함된 경우 결과가 NA가 됨
  • sum(), mean()과 같은 함수에서도 vector object의 원소 중 결측치가 있는 경우 결과가 NA가 됨
  • 단, sum(), mean()과 같은 함수의 경우, 함수 내부에 결측치를 처리할 수 있는 옵션이 존재
    • na.rm=FALSE(default) : NA를 remove하라는 의미
    • help(sum) 또는 ?sum을 통해 help문에서 확인 가능
  • summarise()를 이용해 요약 통계량 산출 가능.
    • na.rm=FALSE(default) : NA를 remove하라는 의미
8 + 2 + NA
8 - 2 + NA
8 * 2 * NA
NA / 2
NA * NA
NA <= 1


df <- data.frame(sex = c("M","F","F",NA,"M"),
                 grade = c(5,4,3,2,NA))
df
mean(df$grade, na.rm = T) # 결측치 제외하고 평균 산출
sum(df$grade, na.rm=T)    # 결측치 제외하고 합계 산출


library(dplyr)
df %>% summarise( max_grade = max(grade, na.rm=TRUE),
                  mean_grade = mean(grade, na.rm=TRUE),
                  sum_grade = sum(grade, na.rm=TRUE))


age <- c(58, 78, 44, 88, 999, 13, 26, 999)   # 999를 결측치를 사용한 경우
age[age == 999] <- NA                        # 999 부분을 NA로 치환
summary(age)
sum(age)                  # 결측치 포함하여 합계 계산
sum(age, na.rm=TRUE)      # 결측치 제외하여 합계 계산


  • apply() 함수에서 mean(), sum() 등을 적용할 때, na.rm=T를 반영하고 싶은 경우, FUN 옵션 뒤에 na.rm=T 인자를 추가
apply(patient[, 1:2], 2, mean, na.rm=T)
apply(patient[, 1:2], 1, sum, na.rm=T)


이상치

정상 범주에서 크게 벗어난 값을 이상치(Outlier)라고 한다.
데이터 수집 과정에서 오류가 발생할 수 있음.
이상치가 포함되어 있으면 분석 결과가 왜곡되므로 이상치를 제거하는 작업이 필요함.

이상치제거 - 존재할 수 없는 값

  • 논리적으로 존재할 수 없는 값이므로 분명한 오류값이다. 따라서 제거 필요.
# sex: 남1, 여2; score: 1~5점;
outlier <- data.frame(sex = c(1, 2, 1, 3, 2, 1),
                      score = c(5, 4, 3, 4, 2, 6))
outlier
##   sex score
## 1   1     5
## 2   2     4
## 3   1     3
## 4   3     4
## 5   2     2
## 6   1     6
# 1. 이상치 확인
table(outlier$sex)
## 
## 1 2 3 
## 3 2 1
table(outlier$score)
## 
## 2 3 4 5 6 
## 1 1 2 1 1
# 2. 결측치 처리
outlier$sex <- ifelse(outlier$sex == 3, NA, outlier$sex)
outlier$score <- ifelse(outlier$score > 5, NA, outlier$score)
outlier
##   sex score
## 1   1     5
## 2   2     4
## 3   1     3
## 4  NA     4
## 5   2     2
## 6   1    NA
# 3. 결측치 제거 또는 대체 
# 코드 생략-------


이상치제거 - 극단적인 값

  • 논리적으로 존재할 수 있으나 극단적으로 작거나 큰 값을 ’극단치’라고 함.
  • 몸무게가 200kg 이상의 값이 존재할 가능성은 있으나, 드문 경우이므로 극단치이다. 분석 결과 왜곡을 방지하기 위해 제거 필요.
    • 논리적 기준 : 성인 몸무게 40kg ~ 150kg을 벗어나면 극단치
    • 통계적 기준 : 상자 그림에서 1.5 IQR 벗어나면 극단치
# mpg 데이터(자동차 234종의 연비 관련 정보) 불러오기
# drv: 구동 방식(f = 전륜구동, r = 후륜구동, 4 = 사륜구동)
mpg <- data.frame(ggplot2::mpg) 

# 1. boxplot 그리기
boxplot(mpg$hwy)


# 2. 통계치 확인
# : 아래쪽 극단치 경계, 1사분위수, 중앙값, 3사분위수, 위쪽 극단치 경계(출력결과: 위에서 아래순)
boxplot(mpg$hwy)$stats
##      [,1]
## [1,]   12
## [2,]   18
## [3,]   24
## [4,]   27
## [5,]   37
# 3. 결측치 처리
mpg$hwy <- ifelse(mpg$hwy < 12 | mpg$hwy > 37, NA, mpg$hwy)

# 4. 결측치 확인
table(is.na(mpg$hwy))
## 
## FALSE  TRUE 
##   231     3
# 5. 간단한 분석
mpg %>% group_by(drv) %>% 
  summarise(mean_hwy = mean(hwy, na.rm=T))
## # A tibble: 3 x 2
##   drv   mean_hwy
##   <chr>    <dbl>
## 1 4         19.2
## 2 f         27.7
## 3 r         21


자료 변환

R의 자료형

  • R의 변수타입(variable type): integer, numeric, character, logical, complex 등
  • R의 자료구조(data structure): scalar, vector, factor, matrix, data frame, array, list 등


자료형 관련 함수

구분 자료형 확인 자료형 변환
numeric is.numeric() as.numeric()
character is.character() as.character()
logical is.logical() as.logical()
vector is.vector() as.vector()
factor is.factor() as.factor()
date
as.date()
matrix is.matrix() as.matrix()
data frame is.data.frame() as.data.frame()

날짜형 자료

  • R은 날짜를 취급하는 Date라는 자료구조를 가지고 있음
  • 날짜형 변수로 지정되면 두 날짜간 연산이 가능
  • as.Date(): character 또는 factor형 날짜값을 date로 변환
  • difftime(): 첫 번째 인자의 날짜 빼기 두 번째 인자의 날짜의 차이를 units 단위로 환산
  • difftime()units 지정가능한 값: “auto”, “secs”, “mins”, “hours”, “days”, “weeks”
str(patient)
patient$dxdate <- as.Date(patient$dxdate, format="%Y/%m/%d")  # 진단일을 날짜형으로 변환
class(patient$dxdate)
str(patient)

x <- patient$dxdate[5] - patient$dxdate[1]
x
mode(x)
difftime(patient$dxdate[5], patient$dxdate[1], units="days")
difftime(patient$dxdate[5], patient$dxdate[1], units="weeks")


날짜형 포맷

  • "%Y": 4자리 연도
  • "%m": 2자리 월
  • "%d": 2자리 일
  • as.Date()에서 format 옵션에 character형태(Y,m,d)로 값 지정
  • strptime(): 시간 처리 함수
  • month.name: 월 이름 목록
  • month.abb: 월 이름 약어 목록
as.Date("2023/03/28", format="%Y/%m/%d")
as.Date("12/10/2019", format = "%m/%d/%Y" )
as.Date("01012023", format="%m%d%Y")
as.Date("01012023", format="%Y")
month.name     # 월 이름 목록
month.abb      # 월 이름 약어 목록


now<-strptime("2023-03-28 13:45:00","%Y-%m-%d %H:%M:%S")

now$year # 연도. 1900년을 0으로 표준화한 값. 2023-1900
now$mon  # 월
now$mday # 일
now$hour # 시간
now$min  # 분
now$sec  # 초
now$wday # 요일(일요일 0; 월요일 1; 화요일 2; ...)


날짜 관련 함수

  • Sys.Date(): 현재 날짜를 YYYY-MM-DD의 형태로 반환
  • Sys.time(): 현재 날짜와 시간까지 YYYY-MM-DD hh:mm:ss timezone의 형태로 반환
Sys.Date()
Sys.time()


lubridate 패키지

  • 날짜 연산에 용이한 패키지
  • now(): Sys.time()과 동일
  • year(), month(), day(): 연, 월, 일 추출
  • wday(): 요일 factor 추출 (Sunday가 reference)
  • years(), months(), hours(), minutes(), seconds(): ()안의 수 입력하여 증감연산 가능
# install.packages("lubridate")
library(lubridate)

date <- now()
date

year(date)
month(date)
day(date)
wday(date,label=T)

# 2040년 1월 1일부터 5일간의 날짜 데이터 생성
day1 <- as.Date("2040/01/01", "%Y/%m/%d")
day2 <- as.Date("2040/01/05", "%Y/%m/%d")
newdate <- seq(day1, day2, 1)              
newdate
newdate + 365      # 1년 뒤 날짜로 변경 (윤달이 있는 경우 오류 발생 가능)
newdate + years(1)  # 1년 뒤 날짜로 변경


Import/Export Data

작업 디렉토리(working directory)

  • 사용자가 작업하고 있는 디렉토리
  • 파일을 읽고 쓰기 위해서는 데이터를 읽거나 쓸 디렉토리의 경로 파악이 필요
  • getwd(): 현재 작업 디렉토리 경로 반환
  • setwd("path"): 원하는 디렉토리 경로 지정
getwd()                                # 현재 경로 확인 (Check the current path)
mypath <- getwd()                      # 현재 경로 저장 (Save the current path)
# setwd("C:/Users/User/Desktop")        # 바탕화면으로 경로 변경하기1 (change the path to desktop 1)
# setwd("C:\\Users\\User\\Desktop")     # 바탕화면으로 경로 변경하기2 (change the path to desktop 2)
setwd(mypath)                          # 저장해둔 기존 경로로 변경 (Change to the saved path)


  • paste0()을 이용하여 데이터 경로를 잡아보자.
paste0("a", "bc")
paste0(1:3, c("a","b","c"))

# 작업경로 저장(Save working directory)
#path <- "C:/Users/User/Desktop/data"
path <- paste0(mypath,"/data")
path


read.csv() / write.csv()

  • 엑셀 csv 파일을 데이터 프레임 형태로 불러오는 기능
  • csv (Comma-seperated Values) 파일의 경우, 기본적으로 header가 있고, 열 구분문자가 콤마(,)임
  • 별도의 패키지 설치가 필요하지 않음. R에 내장된 read.csv() 이용해서 불러오기
  • read.csv()의 디폴트 옵션은 header=T, sep=","임. 변수명이 없는 경우, header=F 파라미터 지정
  • write.csv()의 디폴트 옵션은 row.names=T이므로, csv파일의 경우 row.names=F는 따로 기재해주는 것을 권장


read.csv(file,                  # 파일명 (경로 직접 지정 가능)
           header=T,              # 첫줄이 변수명인 경우 TRUE
           sep=",",               # 열구분자. 디폴트는 콤마 "," , 탭이면 "\t"로 변경
           as.is=FALSE            # string을 factor로 인식할 것인지. FALSE이면 문자변수를 factor로 변환. 
           na.strings="NA"        # NA를 결측으로 인지
           nrows =-1              # 2줄만 불러오고 싶을 때 nrows=2 입력. 음수로 설정할 경우 명령어 무시
           skip =0              # 세 번째 줄부터 불러오고 싶으면 skip=2 입력
           )

write.csv(object,               # object (vector, matrix, dataframe 가능)
            file,                 # 저장경로 및 파일명
            quote=T,             # TRUE인 경우 문자형 자료에 "" 붙여 저장, FALSE인 경우 "" 생략되어 저장
            sep=" ",              # 열구분자. 디폴트는 공백. 
            row.names=T,          # 행이름 저장
            col.names=T           # 열이름 저장
            )


read.csv(paste0(path, "/humidity.txt"))
read.csv(paste0(path, "/humidity.txt"), na.strings=c(".", "NA"))
read.csv(paste0(path, "/temperature.txt"), sep=" ")


df1 <- read.csv(paste0(path,"/csv_exam_str.csv"))
str(df1)

df2 <- read.csv(paste0(path,"/csv_exam_str.csv"),as.is = FALSE)
str(df2)

df3 <- read.csv(paste0(path,"/csv_exam_noVar.csv"),header=F)            # 첫 행부터 데이터 시작
head(df3)
str(df3)


outpath <- paste0(path, "/output")

write.csv(df1, paste0(outpath, "/exam.csv"), row.names=F)


x <- c("남","여","남","남")
y <- c(20,21,22,23)
z <- data.frame(x,y)
z

write.csv(z, file = paste0(outpath,"/new_file.csv" ))

# 한글 깨짐 방지: fileEncoding = "UTF-8"
# 문자열에 따옴표 제거: quote=FALSE
write.csv(z, file = paste0(outpath,"/new_file1.csv" ),fileEncoding = "UTF-8", row.names=F)
write.csv(z, file = paste0(outpath,"/new_file2.csv" ),fileEncoding = "UTF-8", row.names=F, quote=FALSE)


xls/xlsx파일

  • read_excel은 엑셀 파일을 데이터 프레임으로 만드는 기능이다.
  • 엑셀 테이블은 csv 저장 후 read.csv()로 읽어들이는 것이 확실함
  • 여러 시트 또는 여러 파일에서 읽어들여야할 경우 csv 변환작업 불편
  • 첫 행부터 데이터가 시작되는 엑셀파일일 경우, col_names = F 파라미터 설정
  • readxl 패키지의 read_excel()로 엑셀파일에 바로 접근하여 데이터 호출 가능
    • readxl 패키지 로드시 r과 java의 비트가 달라 에러 발생 가능
    • 에러 발생 시, java download에서 java를 받아 설치 후 재시도


read_excel( path,             # 엑셀 파일 경로(항상 앞뒤에 따옴표)
            sheet = NULL,     # 특정 Sheet 불러올 때 사용
            col_names = TRUE, # 열 이름 지정
            na = "",          # 빈 셀을 NA로 인식
            skip = 0          # 몇 행을 건너뛰어서 불러올 것인지
)


# install.packages(c("readxl", "writexl"))
library(readxl)
temp <- read_excel(paste0(path, "/temperature.xlsx"))
humid <- read_excel(paste0(path, "/temperature.xlsx"), sheet = 2)                     # 시트 순서를 이용한 호출
humid <- read_excel(paste0(path, "/temperature.xlsx"), sheet = "humidity")            # 시트 이름을 이용한 호출


df1 <- read_excel(paste0(path,"/csv_exam_noVar.xlsx"))
head(df1)
str(df1)

df2 <- read_excel(paste0(path,"/csv_exam_noVar.xlsx"),col_names=F)           # 첫 번째 행이 변수명이 아닐 때
head(df2)
str(df2)

library(writexl)
write_xlsx(temp, paste0(outpath, "/temperature2.xlsx"))



THE END